

如果没有工具来读取注解，那么它其实和注解没什么区别。使用注解的过程中，很重要的一点是创建并使用 <RedSpan>注解处理器</RedSpan>。

java 为反射的 API 提供了扩展，可以方便的创建这些工具。Java 还提供了一个 javac 编译器钩子，用来在编译时使用注解。

以下示例是一个简单的注解处理器，它读取被注解的 PasswordUtils 类，然后利用反射来查找 @UseCase 标签。通过给定的 id 值列表，该注解列出了它找到的所有用例和缺少的用例。

```java
package annotations;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author 21129
 */
public class UseCaseTracker {
  public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
    for (Method method : cl.getDeclaredMethods()) {
      UseCase useCase = method.getAnnotation(UseCase.class);
      if (useCase != null) {
        System.out.println("Found Use Case:" + useCase.id() + " " + useCase.description());
        useCases.remove(new Integer(useCase.id()));
      }
    }
    for (int i : useCases) {
      System.out.println("Warning: Missing use case-" + i);
    }
  }
  public static void main(String[] args) {
    trackUseCases(new ArrayList<>(Arrays.asList(47, 48, 49, 50)), PasswordUtils.class);
  }
}
```

这里同时使用了反射方法 <RedSpan>getDeclaredMethods() 和 从 AnnotatedElement 接口中（诸如 Class、Method、Field等都会实现该接口）继承实现的 getAnnotation() 方法，该方法返回制定类型的注解对象，如果没有就返回 null</RedSpan>

## 注解元素
UserCase 注解有 id 和 description 两个元素，它们是 int 和 String 类型。注解支持的元素类型如下：

1. 基本类型（int、float、double、boolean、char、byte、short、long）
2. String
3. Class
4. enum
5. Annotation
6. 以上类型数组

注解可以作为注解元素类型。

## 默认值的限制
编译器对元素的默认值要求非常苛刻。所有元素都需要有确定的值，这意味着要么有默认值，要么由使用该注解的类来设定值。

还有一个限制就是，在设定默认值时，任何非基本类型元素都不能赋值为 null，这导致很难让处理器去判断某个元素存在与否，因为所有元素在所有注解声明中都是有效存在的。但可以通过检查该元素是否为特殊值（如空字符串或者负值）来绕过这个限制。

```java
package annotations;


/**
 * @author 21129
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimulatingNull {
  int id() default -1;
  String description() default "";
}
```

## 生成外部文件
一些框架要求一些额外信息来配合源代码进行工作，在使用这种框架时。注解特别有用。

Web 服务、自定义标签库、以及 Hibernate 等对象/关系映射工具（ORM）通常也需要代码之外的 XML 描述文件。每定义一个 Java 类，程序员都必须经过一个乏味的配置信息的过程，比如配置类名、包名等——这些都是类中本来就有的信息。这样就会导致得到关于一个类的两个独立的信息源，这会导致代码的信息同步问题。同时也要求程序员除了写 Java 代码外，还必须知道如何编写这些描述符文件。

如果要实现一套基本的 ORM 功能来自动化数据库表的创建，可以通过 XML 描述符文件来指定类名、类中的每一个成员，以及数据库映射信息。而如果使用注解，就可以将所有的信息都维护在单个源文件中。
要实现此功能，需要注解来定义数据库表名、字段信息，以及要隐射到属性的 SQL 类型。

以下示例是一个注解，注解处理器会通过它创建一个数据库表：
```java
package annotations.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 21129
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
  String name() default "";
}
```
在 @Target 中，指定了注解可以应用的位置，这里指定的每个 ElementType（元素类型）都是一条约束，可以指定一个，也可以指定一个列表。

@DBTable 中有个 name() 元素，该注解可以通过它为处理器要创建的数据表指定表名。

下面三个是表字段的注解

```java
package annotations.database;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
  boolean primaryKey() default false;
  boolean allowNull() default true;
  boolean unique() default false;
}
```


```java
package annotations.database;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
  int value() default 0;
  String name() default "";
  Constraints constrains() default @Constraints;
}

```


```java
package annotations.database;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
  String name() default "";
  Constraints constrains() default @Constraints;
}
```

@Constraints 注解可以让处理器提取出数据库表的元数据，这相当于数据库提供的一个小的约束子集，可以帮我我们形成一个整体的概念。

另外两个 @interface 用于定义 SQL 的类型。同样，其它 SQL 类型也需要定义，在这个示例中，只定义了 String 和 int 两种类型就足够了。

这些类型都有一个 name() 元素和一个 constraints() 元素，后者指定字段的数据库约束信息。constraints() 的默认值实际上是一个自身带有一套默认值的 @Constraints 注解。如果想修改内嵌的 @Constraints 注解的默认值，可以像下面这样定义它的元素：

```java
package annotations.database;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Uniqueness {
  Constraints constraints() default @Constraints(unique = true);

}
```

以下示例是一个使用了该注解的简单类。
```java
package annotations.database;
/**
 * @author 21129
 */
@DBTable(name="MEMBER")
public class Member {
  @SQLString(value=30)
  private String firstName;
  @SQLString(value=50)
  private String lastName;
  @SQLInteger
  private Integer age;
  @SQLString(value=30,constrains=@Constraints(primaryKey=true))
  private String reference;
  static int memberCount = 0;

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public String getReference() {
    return reference;
  }

  public void setReference(String reference) {
    this.reference = reference;
  }

  public static int getMemberCount() {
    return memberCount;
  }

  @Override
  public String toString() {
    return "Member{" +
        "firstName='" + firstName + '\'' +
        ", lastName='" + lastName + '\'' +
        ", age=" + age +
        ", reference='" + reference + '\'' +
        '}';
  }
}
```
类注解 @DBTable 被赋值为 MEMBER，以用作表名。属性 firstName 和 lastName 都被注解为 @SQLString ，并且分别被赋值为 30 和 50 。

:::tip
如果注解中的元素名定义为 value, 那么只要它是唯一指定的元素类型，就可以省略该元素名，直接在圆括号中指定它的值。
:::

默认值的语法虽然简单 ，但是如果内嵌注解有特殊的值，那么就无法使用这种语法了。例如，@Constraints 注解的默认值是 @Constraints(unique=true) ，如果要指定 @Constraints(primaryKey=true) ，那么必须使用完整的注解形式。这时，value 也是不能省略的。

可以看出这里并不优雅。

### 替换方案
对于这个问题，可以使用 其它方式来创建注解，举例来说，可以写一个 @TableColumn 的注解类，其中包含一个 enum 元素，来定义注入 STRING、INTEGER、FLOAT 等类型。这样就无需为每个 SQL 类型写一个 @interface 了，但是也无法再用 size（长度）或 precision（精度）等额外的元素来进一步修饰类型，而这些可能会有用。

当然也可以用 String 元素来描述实际的 SQL 类型（比如 VARCHAR(30) 或 INTEGER）。这样可以修饰类型，但却将 Java 类型和 SQL类型的隐射关系在代码中绑定了，这不是好的设计。这样当数据库有变化的时候就需要重新编译一遍代码。

更优雅的方式是，告诉注解处理器你需要什么口味的 SQL，然后注解处理器在执行的时候再来处理这些细节。

第三种可行的方法是同时使用两个注解类型来注解目标字段 —— @Constraints 和相应的 SQL 类型（比如 @SQLInteger）。这不太优雅，但是只要需要，编译器就允许对目标增加多个注解。



注解不支持继承，上面这个例子是目前的最佳选择了。

## 实现注解处理器

下面这个示例演示了注解处理器如何读取类文件，检查其数据库注解，并生成 SQL 命令来创建数据库。

```java
package annotations.database;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * 基于反射的注解处理器
 *
 * @author 21129
 */
public class TableCreator {

  public static void main(String[] args) throws ClassNotFoundException {
    if (args.length < 1) {
      System.out.println("arguments:annotated classes");
      System.exit(0);
    }
    for (String className : args) {
      Class<?> cl = Class.forName(className);
      DBTable dbTable = cl.getAnnotation(DBTable.class);
      if (dbTable == null) {
        System.out.println("No DBTable annotation in class" + className);
        continue;
      }
      String tableName = dbTable.name().toLowerCase();
      // 如果 tableName 为空，则使用 className
      if (tableName.length() < 1) {
        tableName = cl.getName().toLowerCase();
      }
      List<String> columnDefs = new ArrayList<>();
      for (Field field  : cl.getDeclaredFields()) {
        String columnName = null;
        Annotation[] anns = field.getDeclaredAnnotations();
        if (anns.length < 1) {
          continue; // 不是数据库表字段
        }
        if (anns[0] instanceof SQLInteger) {
          SQLInteger sqlInteger = (SQLInteger) anns[0];
          // 如果name 未指定，则使用字段名
          if (sqlInteger.name().length() > 1) {
            columnName = sqlInteger.name();
          } else {
            columnName = field.getName().toLowerCase();
          }
          columnDefs.add(columnName+" INT" +getConstraints(sqlInteger.constrains()));
        }
        if (anns[0] instanceof SQLString) {
          SQLString sqlString = (SQLString) anns[0];
          // 如果name 未指定，则使用字段名
          if (sqlString.name().length() > 1) {
            columnName = sqlString.name();
          } else {
            columnName = field.getName().toLowerCase();
          }
          columnDefs.add(columnName+" VARCHAR("+sqlString.value()+")" +getConstraints(sqlString.constrains()));
        }
      }
      StringBuilder createCommand = new StringBuilder("CREATE TABLE "+tableName+"(");
      for (String columnDef : columnDefs) {
        createCommand.append("\n    "+columnDef+",");
      }
      // 移除末尾的 ,
      String tableString = createCommand.substring(0, createCommand.length()-1)+")";
      System.out.println("Table Creation SQL for "+cl.getName()+" is :\n"+tableString);
    }
  }
  private static String getConstraints(Constraints con){
    String constraints = "";
    if (!con.allowNull()) {
      constraints += " NOT NULL";
    }
    if (con.unique()) {
      constraints += " UNIQUE";
    }
    if (con.primaryKey()) {
      constraints += " PRIMARY KEY";
    }
    return constraints;
  }
}
```

```text title='执行结果'
Table Creation SQL for annotations.database.Member is :
CREATE TABLE member(
    firstname VARCHAR(30),
    lastname VARCHAR(50),
    age INT,
    reference VARCHAR(30) PRIMARY KEY
)
```
